#include "jsapi.h"
#include "js-engine.h"
#include "desktopClasses.h"
#include "mainview-parser.h"

static gboolean dispatchJSScriptInterval(gpointer ptr);
static gboolean dispatchJSScriptTimeout(gpointer ptr);

static JSBool view_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
static JSBool view_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
static JSBool view_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);

static JSBool view_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_appendElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_beginAnimation(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_cancelAnimation(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_clearInterval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_clearTimeout(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_insertElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_removeElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_resizeBy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_resizeTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_setTimeout(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static JSBool view_setInterval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);

static js_engine *engine= NULL;

JSFunctionSpec view_methods[] = { 
    {"alert", view_alert, 1, 0, 0},
    {"appendElement", view_appendElement, 1, 0, 0},
    {"beginAnimation", view_beginAnimation, 4, 0, 0},
    {"cancelAnimation", view_cancelAnimation, 1, 0, 0},
    {"clearInterval", view_clearInterval, 1, 0, 0},
    {"clearTimeout", view_clearTimeout, 1, 0, 0},
    {"confirm", view_confirm, 1, 0, 0},
    {"insertElement", view_insertElement, 2, 0, 0},
    {"removeElement", view_removeElement, 1, 0, 0},
    {"resizeBy", view_resizeBy, 2, 0, 0},
    {"resizeTo", view_resizeTo, 2, 0, 0},
    {"setTimeout", view_setTimeout, 2, 0, 0},
    {"setInterval", view_setInterval, 2, 0, 0},
    {NULL, NULL, 0, 0, 0}
};

struct property_parser view_parser[] = {
    { "caption", set_property_string_value },
    { "height", set_property_integer_value }, 
    { "width", set_property_integer_value }, 
    { "resizable", set_property_string_value }, 
    { "showCaptionAlways", set_property_boolean_value }, 
    { NULL, NULL }, 
};

JSClass viewClass = { 
    "view", JSCLASS_HAS_PRIVATE, view_AddProperty,
    JS_PropertyStub, view_GetProperty, view_SetProperty, JS_EnumerateStub,
    JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

JSClass eventClass = {
    "event", 0, JS_PropertyStub, JS_PropertyStub,
    JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub,
    JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS
};

static JSBool view_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
    return TRUE;
}

static JSBool view_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
    return TRUE;
}

static JSBool view_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) {
    return TRUE;
}

static gboolean dispatchJSScriptInterval(gpointer ptr) {
    JSScript *script = (JSScript *)ptr;
    jsval rval;
    g_return_val_if_fail(engine, FALSE);
    JS_EndRequest(engine->cx);
    if (JS_ExecuteScript(engine->cx, engine->view, script, &rval) == JS_FALSE)
        g_warning("setInterval() failed.");
    return TRUE;
}

static gboolean dispatchJSScriptTimeout(gpointer ptr) {
    JSScript *script = (JSScript *)ptr;
    jsval rval;
    g_return_val_if_fail(engine, FALSE);
    JS_EndRequest(engine->cx);
    if (JS_ExecuteScript(engine->cx, engine->view, script, &rval) == JS_FALSE)
        g_warning("setTimeout() failed.");
    return FALSE;
}

static gboolean dispatchJSFunctionInterval(jsval func) {
    jsval args, rval;
    g_return_val_if_fail(engine, FALSE);
    JS_EndRequest(engine->cx);
    if(JS_CallFunctionValue(engine->cx, engine->view, func, 0, &args, &rval) == JS_FALSE)
	g_warning("setInterval() failed.");
    return FALSE;
}

static gboolean dispatchJSFunctionTimeout(jsval func) {
    jsval args, rval;
    g_return_val_if_fail(engine, FALSE);
    JS_EndRequest(engine->cx);
    if(JS_CallFunctionValue(engine->cx, engine->view, func, 0, &args, &rval) == JS_FALSE);
	g_warning("setTimeout() failed.");
    return FALSE;
}

static JSBool view_alert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    desktop_element *widget = JS_GetPrivate(cx, obj);
    GString *msg = g_string_new(NULL);

    if (argc >= 1 && JSVAL_IS_STRING(argv[0]))
        g_string_append(msg, JS_GetStringBytes(JSVAL_TO_STRING(argv[0])));

    if (widget && widget->view) {
	GtkWindow *window = (GtkWindow *)gtk_widget_get_toplevel(widget->view);
        GtkWidget *dialog = gtk_message_dialog_new(window, 0, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, msg->str);

        gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
    }
    g_string_free(msg, TRUE);
    return JS_TRUE;
}

static JSBool view_appendElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_beginAnimation(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_cancelAnimation(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_clearInterval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    if (argc != 1) {
        return JS_FALSE;
    }
    if (JSVAL_IS_INT(argv[0])) {
        GSource *thread_src;
        guint thread_id= JSVAL_TO_INT(argv[0]);
        if(thread_id > 0){
            thread_src = g_main_context_find_source_by_id(NULL, thread_id);
	    if(thread_src)
		g_source_destroy(thread_src);
	    JS_EndRequest(cx);
        }
        return JS_TRUE;
    } else {
        return JS_TRUE;
    }
}

static JSBool view_clearTimeout(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    if (argc != 1) {
        return JS_FALSE;
    }
    if (JSVAL_IS_INT(argv[0])) {
        GSource *thread_src;
        guint thread_id= JSVAL_TO_INT(argv[0]);
        if(thread_id > 0){
            thread_src = g_main_context_find_source_by_id(NULL, thread_id);
	    if(thread_src)
		g_source_destroy(thread_src);
	    else
		g_warning("invalid thread id %d was cleared.",thread_id);
	    JS_EndRequest(cx);
        }
        return JS_TRUE;
    } else {
        return JS_FALSE;
    }
}

static JSBool view_confirm(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_insertElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_removeElement(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_resizeBy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_resizeTo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

static JSBool view_setTimeout(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    guint interval_msec = 0;
    guint thread_id = 0;
    GString *cmd= NULL;
    JSScript *script= NULL;

    if (argc != 2) 
        return JS_FALSE;
    if(!JSVAL_IS_INT(argv[1]))
	return JS_FALSE;

    interval_msec = JSVAL_TO_INT(argv[1]);

    if (JSVAL_IS_STRING(argv[0])) {
        cmd = g_string_new("this.");
        g_string_append(cmd, JS_GetStringBytes(JSVAL_TO_STRING(argv[0])));
        g_string_append(cmd, ";");

        script = JS_CompileScript(cx, obj, cmd->str, cmd->len, NULL, 0);
        g_string_free(cmd, TRUE);
        g_return_val_if_fail(script, JS_FALSE);

        thread_id = g_timeout_add(interval_msec, dispatchJSScriptTimeout, script);
        *rval = INT_TO_JSVAL(thread_id);
	JS_BeginRequest(cx);
        return JS_TRUE;
    } else if (JSVAL_IS_OBJECT(argv[0]) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[0]))){
	thread_id = g_timeout_add(interval_msec, dispatchJSFunctionTimeout, argv[0]);
	if(thread_id == 0){
	    g_warning("Failed setTimeout.");
	    return JS_FALSE;
	}
	*rval = INT_TO_JSVAL(thread_id);
	JS_BeginRequest(cx);
	return JS_TRUE;
    }
    return JS_FALSE;
}

static JSBool view_setInterval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    guint interval_msec = 0;
    guint thread_id = 0;
    GString *cmd= NULL;
    JSScript *script= NULL;

    if (argc != 2) {
        return JS_FALSE;
    }

    if (!JSVAL_IS_INT(argv[1])) {
        return JS_FALSE;
    }

    if (JSVAL_IS_STRING(argv[0])) {
        cmd = g_string_new("this.");
        g_string_append(cmd, JS_GetStringBytes(JSVAL_TO_STRING(argv[0])));
        g_string_append(cmd, ";");

        interval_msec = JSVAL_TO_INT(argv[1]);

        script = JS_CompileScript(cx, obj, cmd->str, cmd->len, NULL, 0);
        g_string_free(cmd, TRUE);
        g_return_val_if_fail(script, JS_FALSE);

        thread_id = g_timeout_add(interval_msec, dispatchJSScriptInterval, script);
        *rval = INT_TO_JSVAL(thread_id);
	JS_BeginRequest(cx);
        return JS_TRUE;
    } else if (JSVAL_IS_OBJECT(argv[0]) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[0]))) {
        thread_id = g_timeout_add(interval_msec, dispatchJSFunctionInterval, argv[0]);
	if(thread_id == 0){
	    g_warning("Failed setInterval.");
	    return JS_FALSE;
	}
        *rval = INT_TO_JSVAL(thread_id);
	JS_BeginRequest(cx);
        return JS_TRUE;
    }
    return JS_FALSE;
}

static JSBool view_new(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
    return JS_TRUE;
}

gboolean viewClass_init(js_engine *new) {
    g_return_val_if_fail(new, FALSE);
    engine = new;
    return TRUE;
}
